package mvc;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Match URL by regular expression<br/>
 * The maximum number of parameters is 10(from 0 to 10).
 * 
 * @author Michael Liao (askxuefeng@gmail.com)
 * @author LW
 */
final class UrlMatcher {
	static final String[] 	EMPTY_STRINGS 	= new String[0];
	static final String 	SAFE_CHARS 		= "/$-_.+!*'(),";

    final Log 		log 			= LogFactory.getLog(getClass());
    final String 	url;
    final Pattern 	pattern;
    final int[] 	orders;
    final int 		argumentCount;

    /**
     * Build UrlMatcher by given url like "/blog/$1/$2.htm".
     * 
     * @param url Url may contains $1, $2, ... $9.
     */
    public UrlMatcher(String url,int argumentCount) {
        this.url = url;
        this.argumentCount = argumentCount;
        StringBuilder sb = new StringBuilder(url.length() + 20);
        sb.append('^');
        List<Integer> paramList = new ArrayList<Integer>();
        Set<Integer> paramSet = new HashSet<Integer>();
        int start = 0;
        int n,c;
        while (true) {
        	n = url.indexOf('$', start);
        	c = url.charAt(n+1);
            if (n!=(-1) && n<url.length()-1 && c>='0' && c<='9') {
                // get index:
                int i = url.charAt(n+1) - '0';
                
                if(i > argumentCount - 1){
                	throw new ConfigException("argument index out of bounds.");
                }
                
                // $x found:
                paramSet.add(i);
                paramList.add(i);
                addExactMatch(sb, url.substring(start, n));
                sb.append("([^/]*)");
                start = n + 2;
            } else {
                // $x not found!
                addExactMatch(sb, url.substring(start, url.length()));
                break;
            }
        }
        // check parameters:
        if (paramList.size()!=paramSet.size()) {
            throw new ConfigException("Duplicate parameters.");
        }
        
        this.orders = new int[paramList.size()];
        for (int i=0; i<paramList.size(); i++) {
            this.orders[i] = paramList.get(i);
        }
        sb.append('$');
        System.out.println(sb);
        this.pattern = Pattern.compile(sb.toString());
    }

    void addExactMatch(StringBuilder sb, String s) {
        for (int i=0; i<s.length(); i++) {
            char c = s.charAt(i);
            if (c>='a' && c<='z'){
                sb.append(c);
            } else if (c>='A' && c<='Z'){
                sb.append(c);
            } else if (c>='0' && c<='9'){
                sb.append(c);
            } else {
                int n = SAFE_CHARS.indexOf(c);
                if (n==(-1)) {
                    // need encoding:
                    log.warn("Warning: URL contains unsafe character '" + c + "'.");
                    sb.append("\\u").append(toHex(c));
                } else {
                    sb.append(c);
                }
            }
        }
    }


    String toHex(char c) {
        int i = c;
        return Integer.toHexString(i).toUpperCase();
    }

    /**
     * Test if the url is match the regex. If matched, the parameters are 
     * returned as String[] array, otherwise, null is returned.
     * 
     * @param url The target url.
     * @return String[] array or null if url is not match.
     */
    public String[] getUrlParameters(String url) {
        Matcher m = pattern.matcher(url);
        if (!m.matches()){
            return null;
        } if (orders.length==0){
            return EMPTY_STRINGS;
        }
        String[] params = new String[orders.length];
        for (int i=0; i<orders.length; i++) {
            params[i] = m.group(i+1);
        }
        return params;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj==this)
            return true;
        if (obj instanceof UrlMatcher) {
            return ((UrlMatcher)obj).url.equals(this.url);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return url.hashCode();
    }

}